package org.zhadoop.stream.cql;

import java.util.ArrayList;
import java.util.List;

import org.zhadoop.stream.cql.exception.CQLException;
import org.zhadoop.stream.cql.hooks.CommandAnalyzehook;
import org.zhadoop.stream.cql.hooks.CreateDatasourceAnalyzehook;
import org.zhadoop.stream.cql.hooks.CreateStreamAnalyzehook;
import org.zhadoop.stream.cql.hooks.DriverCleanerHook;
import org.zhadoop.stream.cql.hooks.DriverRunHook;
import org.zhadoop.stream.cql.hooks.InsertAnalyzeHook;
import org.zhadoop.stream.cql.hooks.InsertUserOperatorAnalyzeHook;
import org.zhadoop.stream.cql.hooks.MultiInsertAnalyzeHook;
import org.zhadoop.stream.cql.hooks.PreviousValidateHook;
import org.zhadoop.stream.cql.hooks.SelectsAnalyzeHook;
import org.zhadoop.stream.cql.hooks.SemanticAnalyzeHook;
import org.zhadoop.stream.cql.semanticanalyzer.parser.IParser;
import org.zhadoop.stream.cql.semanticanalyzer.parser.ParserFactory;
import org.zhadoop.stream.cql.semanticanalyzer.parser.context.ParseContext;
import org.zhadoop.stream.cql.tasks.Task;
import org.zhadoop.stream.cql.tasks.TaskFactory;
import org.zhadoop.streaming.config.StreamingConfig;
import org.zhadoop.streaming.exception.ErrorCode;

/**
 * CQL解析总类
 * <p/>
 * 命令提交的时候，各类命令都可以无序提交
 * 可以通过explain的方式查看当前提交的命令构建的应用程序结果
 * 并且将应用程序导出为执行计划并对执行计划进行修改
 * <p/>
 * 可以通过load命令导入执行计划
 * <p/>
 * 可以通过submit命令提交当前的应用程序
 * <p/>
 * load和submit命令都会导致driver对象中cql做出的临时修改清空，但是explain不会。
 * <p/>
 * explain和submit命令都会导致对当前已经提交的cql语句进行编译生成执行计划
 * <p/>
 * 提交load命令之前和submit命令之后，都会清空临时变量
 * 允许直接submit而不经过explain，这样，系统会使用默认参数
 * <p/>
 * 通过load提交的应用程序，没办法修改，只能直接submit
 * 但是在命令行中提交的cql语句，只要没有submit，就可以进行修改。
 * 
 * @author zwd
 *
 */
public class Driver implements DriverRunHook{

	private List<DriverRunHook> driverRunHooks = new ArrayList<DriverRunHook>();
	
	private List<SemanticAnalyzeHook> analyzeHooks = new ArrayList<>();
	
	private DriverContext context;
	
	private IParser parser;
	
	private StreamingConfig config = null;
	
	public Driver(){
		config = new StreamingConfig();
		context = new DriverContext();
		parser = ParserFactory.createApplicationParser();
		
		driverRunHooks.add(new DriverCleanerHook());
		
		analyzeHooks.add(new CreateStreamAnalyzehook());
		analyzeHooks.add(new CreateDatasourceAnalyzehook());
		analyzeHooks.add(new CommandAnalyzehook());
		analyzeHooks.add(new SelectsAnalyzeHook());
		analyzeHooks.add(new InsertAnalyzeHook());
		analyzeHooks.add(new PreviousValidateHook());
		analyzeHooks.add(new InsertUserOperatorAnalyzeHook());
		analyzeHooks.add(new MultiInsertAnalyzeHook());
	}
	
	@Override
	public String toString() {
		return super.toString();
	}
	
	/**
	 * CQL 运行起点
	 * <p/>
	 * 1、编译
	 * 2、语义分析
	 * 4、命令执行
	 * 5、返回结果
	 * <p/>
	 * 除了一些查询性的命令之外，其他是不会有结果的。
	 * <p/>
	 * 提交的load命令之前和submit 命令之后，都会清空所有的临时变量
	 * <p/>
	 * 每次提交新的CQL语句，都会导致已经完成解析的应用程序被清空
	 * 因为新提交的逻辑，都会导致应用程序发生变化。
	 * 
	 */
	public void run(String cql) throws CQLException{
		ParseContext parseContext = parser.parse(cql);
		if(parseContext == null){
			CQLException exception = new CQLException(ErrorCode.UNKNOWN_ERROR);
			throw exception; 
		}
		
		saveAllChangeableCQLs(cql, parseContext);
		preDriverRun(context, parseContext);
		try{
			executeTask(parseContext);
		}finally{
			postDriverRun(context, parseContext);
		}
		
		
	}
	
	private void executeTask(ParseContext parseContext) 
	throws CQLException{
		mergeConfs();
		Task task = TaskFactory.createTask(context,parseContext,config,analyzeHooks);
		task.execute(parseContext);
		context.setQueryResult(task.getResult());
	}

	
	private void mergeConfs() {
		if(context.getUserConfs() != null){
			config.putAll(context.getUserConfs());
		}
	}
	
	public CQLResult getResult(){
		return context.getQueryResult();
	}

	/**
	 * 保存所有对查询结果会造成影响的sql语句
	 * @param cql
	 * @param parseContext
	 */
	private void saveAllChangeableCQLs(String cql, ParseContext parseContext) {
		if(CQLUtils.isChangebleCommond(parseContext)){
			context.addCQLs(cql);
		}
	}

	@Override
	public void preDriverRun(DriverContext context, ParseContext parseContext) {
		for(DriverRunHook hook : driverRunHooks){
			hook.preDriverRun(context, parseContext);
		}
	}

	@Override
	public void postDriverRun(DriverContext context, ParseContext parseContext) {
		for(DriverRunHook hook : driverRunHooks){
			hook.postDriverRun(context, parseContext);
		}
	}
	
	/**
	 * 清空driver 中的内容
	 */
	public void clean(){
		context.clean();
		this.config = new StreamingConfig();
	}

}
